/**
 * Copyright (c) 2024 - CARThunder
 * All Rights Reserved by Chengdu CARThunder Intelligent Technology Co., LTD and its affiliates.
 * You may not use, copy, distribute, modify, transmit in any form this file
 * except in compliance with CARThunder in writing by applicable law.
 * <p>
 * <p>
 * file description.
 *
 * @file filename
 * @brief brief function description.
 * @details detailed function description.
 * @version 1.0
 * @author author_name
 * @date last_modified_date
 * <p>
 * Edit History
 * ----------------------------------------------------------------------------
 * DATE                     NAME               DESCRIPTION
 * 2024-07-30              zhihuazhang         Create it.
 * <p>
 * file description.
 * @file filename
 * @brief brief function description.
 * @details detailed function description.
 * @version 1.0
 * @author author_name
 * @date last_modified_date
 * <p>
 * Edit History
 * ----------------------------------------------------------------------------
 * DATE                     NAME               DESCRIPTION
 * 2024-07-30              zhihuazhang         Create it.
 */

/**
 * file description.
 * @file filename
 * @brief brief function description.
 * @details detailed function description.
 * @version 1.0
 * @author author_name
 * @date last_modified_date
 *
 *      Edit History
 *      ----------------------------------------------------------------------------
 *      DATE                     NAME               DESCRIPTION
 *      2024-07-30              zhihuazhang         Create it.
 *
 */

package com.cart.musicdemo.base.service;

import com.cart.musicdemo.model.SongModel;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * The type File system manager.
 */
public class FileSystemManager implements Comparator<File> {

    /**
     * 文件构建路径.
     */
    private static String buildPath;

    /**
     *支持的文件格式.
     */
    private static final String[] SUPPORTED_AUDIO_EXTENSIONS
        = {".mp3", ".wav", ".ogg", ".flac", ".aac", ".m4a"};

    /**
     * 最大递归深度（最大感知文件夹层数）.
     */
    public static final int TREE_MAX_DEPTH = 6;

    /**
     * 歌曲专辑分类.
     */
    private Map<String, List<SongModel>> albumClassification;

    /**
     * 歌曲歌手分类.
     */
    private Map<String, List<SongModel>> singerClassification;

    /**
     * 用来存储所有的文件夹节点，方便在获取下一个节点的时候不用再去全遍历整个文件树.
     */
    private final Map<String, FolderNode> nodeMap;

    /**
     * 文件夹根节点.
     */
    private static FolderNode rootNode;

    /**
     * 所有SongModel的平铺列表.
     */
    private static List<SongModel> allSongModelFlattened;

    /**
     * 歌曲批处理解析器.
     */
    private final BatchSongRetriever batchSongRetriever;

    /**
     * 双检锁单例模式.
     */
    private static volatile FileSystemManager instance;

    private static final String UNKNOWN_SINGER = "未知歌手";

    private static final String UNKNOWN_ALBUM = "未知专辑";

    private static final String TAG = FileSystemManager.class.getName();

    private FileSystemManager() {
        nodeMap = new HashMap<>();
        allSongModelFlattened = new ArrayList<>();
        // 初始化批量解析对象, 必须在buildFolderTree方法之前，因为等下生成树要调用
        batchSongRetriever = new BatchSongRetriever();
        rootNode = buildFolderTree(new File(buildPath), 0);
    }

    /**
     * Gets instance.
     *
     * @return the instance
     */
    public static FileSystemManager getInstance() {
        if (instance == null) {
            synchronized (FileSystemManager.class) {
                if (instance == null) {
                    instance = new FileSystemManager();
                }
            }
        }
        return instance;
    }

    /**
     * 设置文件扫描路径.
     * @param path U盘动态路径
     */
    public static void setBuildPath(String path) {
        buildPath = path;
    }

    /**
     * Gets song model flattened.
     *
     * @return the song model flattened
     */
    public List<SongModel> getSongModelFlattened() {
        return allSongModelFlattened;
    }

    /**
     * Gets root node.
     *
     * @return the root node
     */
    public FolderNode getRootNode() {
        return rootNode;
    }

    /**
     * Search files list.
     *
     * @param keyword the keyword
     * @return the list
     */
    public List<SongModel> searchFiles(String keyword) {
        List<SongModel> matchedFiles = new ArrayList<>();
        searchFileRecursively(rootNode, keyword, matchedFiles);
        return matchedFiles;
    }

    /**
     * 获取文件夹下一层的文件信息.
     * 使用了nodeMap来做缓存处理，避免了每次获取都要遍历全树的操作。
     *
     * @param folderPath the folder path
     * @return the next level result
     */
    public List<Object> getNextLevelResult(String folderPath) {
        FolderNode node = nodeMap.get(folderPath);
        List<Object> result = new ArrayList<>();
        if (node != null) {
            result.addAll(node.getChildren());
            result.addAll(node.getSongModels());
        }
        return result;
    }

    /**
     * 获取依据歌手分类数据.
     *
     * @return 歌手分类数据
     */
    @SuppressWarnings("all")
    public Map<String, List<SongModel>> getSingerClassification() {
        if (singerClassification == null) {
            singerClassification = classify(
                model -> Optional.ofNullable(model.getSinger()).orElse(UNKNOWN_SINGER)
            );
        }
        return singerClassification;
    }

    /**
     * 获取依据专辑分类数据.
     *
     * @return 专辑分类数据
     */
    @SuppressWarnings("all")
    public Map<String, List<SongModel>> getAlbumClassification() {
        if (albumClassification == null) {
            albumClassification = classify(
                model -> Optional.ofNullable(model.getAlbum()).orElse(UNKNOWN_ALBUM)
            );
        }
        return albumClassification;
    }

    @SuppressWarnings("all")
    private Map<String, List<SongModel>> classify(Function<SongModel, String> keyMapping) {
        return allSongModelFlattened.stream().collect(Collectors.groupingBy(keyMapping::apply));
    }

    /**
     * 获取默认播放的第一首歌曲.
     */
    public SongModel getDefaultSong() {
        return getFirstSong(rootNode);
    }

    private SongModel getFirstSong(FolderNode node) {
        List<SongModel> songModels = node.getSongModels();
        if (!songModels.isEmpty()) {
            return songModels.get(0);
        }

        for (FolderNode child : node.getChildren()) {
            SongModel firstSong = getFirstSong(child);
            if (firstSong != null) {
                return firstSong;
            }
        }

        return null;
    }

    @SuppressWarnings("all")
    private FolderNode buildFolderTree(File parentFolder, int depth) {
        FolderNode parentNode = new FolderNode(
            parentFolder.getAbsolutePath(),
            parentFolder.getName()
        );
        // 初始化节点Map
        nodeMap.put(parentNode.getFolderPath(), parentNode);

        File[] files = parentFolder.listFiles();
        if (files != null) {
            // 为了保证文件的排序符合：以名称进行排序，数字->中文->小写英文->大写英文->特殊字符
            Arrays.sort(files, this::compare);
            List<Future<SongModel>> futures = new ArrayList<>();
            for (File file : files) {
                if (file.isDirectory() && file.listFiles().length > 0 && depth < TREE_MAX_DEPTH) {
                    FolderNode childNode = buildFolderTree(file, depth + 1);
                    parentNode.addChildren(childNode);
                } else if (file.isFile() && isAudioFile(file)) {
                    try {
                        Future<SongModel> retrieve = batchSongRetriever.retrieve(file);
                        futures.add(retrieve);
                    } catch (InterruptedException exception) {
                        throw new RuntimeException(exception);
                    }
                }
            }
            for (Future<SongModel> future : futures) {
                try {
                    SongModel songModel = future.get();
                    if (songModel != null) {
                        parentNode.addSongModel(songModel);
                        allSongModelFlattened.add(songModel);
                    }
                } catch (ExecutionException | InterruptedException exception) {
                    throw new RuntimeException(exception);
                }
            }
        }
        return parentNode;
    }

    private boolean isAudioFile(File file) {
        for (String extension : SUPPORTED_AUDIO_EXTENSIONS) {
            if (file.getName().toLowerCase().endsWith(extension)) {
                return true;
            }
        }
        return false;
    }

    @SuppressWarnings("all")
    private void searchFileRecursively(FolderNode node, String keyword,
                                       List<SongModel> matchedModels) {
        for (SongModel songModel : node.getSongModels()) {
            if (Optional.ofNullable(songModel.getPath()).orElse("").contains(keyword)) {
                matchedModels.add(songModel);
            }
        }
        for (FolderNode child : node.getChildren()) {
            searchFileRecursively(child, keyword, matchedModels);
        }
    }

    @Override
    public int compare(File f1, File f2) {
        // 首先比较数字
        int f1Num = getNumberPrefix(f1.getName());
        int f2Num = getNumberPrefix(f2.getName());
        if (f1Num != f2Num) {
            return Integer.compare(f1Num, f2Num);
        }

        // 然后比较中文
        int f1ChineseCompare = compareChineseCharacters(f1.getName());
        int f2ChineseCompare = compareChineseCharacters(f2.getName());
        if (f1ChineseCompare != f2ChineseCompare) {
            return f1ChineseCompare - f2ChineseCompare;
        }

        // 最后比较英文
        return f1.getName().compareToIgnoreCase(f2.getName());
    }

    private int getNumberPrefix(String name) {
        int index = 0;
        while (index < name.length() && Character.isDigit(name.charAt(index))) {
            index++;
        }
        return index > 0 ? Integer.parseInt(name.substring(0, index)) : 0;
    }

    private int compareChineseCharacters(String name) {
        int chineseCount = 0;
        for (int i = 0; i < name.length(); i++) {
            if (isChinese(name.charAt(i))) {
                chineseCount++;
            }
        }
        return chineseCount;
    }

    private boolean isChinese(char ch) {
        return Character.UnicodeBlock.of(ch) == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS;
    }

    /**
     * The type Folder node.
     */
    public static class FolderNode {
        private final String folderPath;
        private final String name;
        private final List<SongModel> songModels;
        private final List<FolderNode> children;

        /**
         * Instantiates a new Folder node.
         *
         * @param folderPath the folder path
         */
        FolderNode(String folderPath, String name) {
            this.name = name;
            this.folderPath = folderPath;
            children = new ArrayList<>();
            songModels = new ArrayList<>();
        }

        /**
         * Add children.
         *
         * @param folderNode the folder node
         */
        public void addChildren(FolderNode folderNode) {
            children.add(folderNode);
        }

        /**
         * Add song model.
         *
         * @param model the model
         */
        public void addSongModel(SongModel model) {
            songModels.add(model);
        }

        /**
         * Get file name.
         * @return filename
         */
        public String getName() {
            return name;
        }

        /**
         * Gets folder path.
         *
         * @return the folder path
         */
        public String getFolderPath() {
            return folderPath;
        }

        /**
         * Gets children.
         *
         * @return the children
         */
        public List<FolderNode> getChildren() {
            return children;
        }

        /**
         * Gets song models.
         *
         * @return the song models
         */
        public List<SongModel> getSongModels() {
            return songModels;
        }
    }
}